#ifndef __M_SRV_H__
#define __M_SRV_H__

#include "matcher.hpp"
#include "online.hpp"
#include "room.hpp"
#include "session.hpp"
#include "util.hpp"
#include "db.hpp"

#define WWWROOT "./wwwroot/"

//server服务器需要把之前写的所有内容全部整合到一起！！！！！！
class gobang_server
{
private:
    std::string _web_root;//静态资源根目录 ./wwwroot/      /register.html ->  ./wwwroot/register.html
    user_table _ut;
    online_manager _om;
    room_manager _rm; 
    matcher _mm;
    session_manager _sm;
    wsserver_t _wssrv;
private:
    //这四个函数都是基于http协议需要做的业务------和下文的http_callback函数相对应！！！！！！


    //-------------------------------------------------------------------------------------------------------------------------

    void file_handle(wsserver_t:: connection_ptr &conn)
    {
        //1.获取uri
        websocketpp::http::parser::request req = conn->get_request();//使用get_request()获取发送过来的信息的req格式
        std::string uri = req.get_uri();//
        //2.拼接文件路径
        std::string pathname = _web_root + uri;
        //3.如果uri是个“/”,那么需要默认转为“/login.html”！！！！！！！！--------这个是默认处理

        size_t pos;
        while ((pos = pathname.find("//")) != std::string::npos) {
            pathname.replace(pos, 2, "/");
        }

        if(pathname.back() == '/')
        {
            pathname += "login.html";
        }
        //4.读取文件
        std::string body;//正文
        bool ret = file_util::read(pathname, body);
        if(ret = false)
        {
            body += "<html>";
            body += "<head>";
            body += "<meta charset='UTF-8'/>";//是中文可以显示在界面上
            body += "</head>";
            body += "<body>";
            body += "<h1> NOT FOUND </h1>";
            body += "</body>";
            conn->set_status(websocketpp::http::status_code::not_found);
            conn->set_body(body);
             return ;
        }
        //5.设置正文
        conn->set_status(websocketpp::http::status_code::ok);
        conn->set_body(body);
    }
    //获取静态资源，例如register的html页面---------------------------------------------------------------------------------------------下面的都是动态请求
    
    //----------------------------------------------------------------------------下面的都是动态请求---------------------------------------------------

    void login_handle(wsserver_t:: connection_ptr &conn){
        //2.对正文进行反序列化，获取username和password-------直接从conn中使用get_request_body()获取
        std::string req_body = conn->get_request_body();

        Json::Value log_info;
        bool ret = json_util::unserialize(req_body,log_info);//获取的正文放到log_info中
        if(ret == false)
        {
            DBG_LOG("反序列化失败");
            http_resp(conn,false,websocketpp::http::status_code::bad_request,"请求的正文格式错误");
            return;
        }

        //判断用户名和密码是否完整！！！！！！
        if (log_info["username"].isNull() || log_info["password"].isNull())
        {
            DBG_LOG("用户名/密码不完整");
            http_resp(conn,false,websocketpp::http::status_code::bad_request,"请输入完整的用户名和密码");
            return;
        }

        //3.登录数据库判断
        ret = _ut.login(log_info);
        if (ret == false) {
        //  1. 如果验证失败，则返回400
        DBG_LOG("用户名密码错误");
        return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名密码错误");
        }

        //4.创建session！！！！！！！！！！！！！
        uint64_t uid = log_info["id"].asUInt64();
        session_ptr ssp =  _sm.create_session(uid, LOGIN);
        if(ssp.get()==nullptr)
        {
            DBG_LOG("创建会话失败");
            return http_resp(conn, false, websocketpp::http::status_code::internal_server_error, "创建会话失败");
        }
        _sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);
        //4. 设置响应头部：Set-Cookie,将sessionid通过cookie返回--------------------------------------------------!!!!!!!!!!
        std::string cookie_ssid = "SSID=" + std::to_string(ssp->ssid());
        conn->append_header("Set-Cookie", cookie_ssid);
        return http_resp(conn,true,websocketpp::http::status_code::ok,"用户登录成功");
    }
    //用户登录！！！


    //返回的报文的设置！！！！！
    void http_resp(wsserver_t:: connection_ptr &conn,bool result,websocketpp::http::status_code::value code,const std::string &reason)
    {
         Json::Value resp;
         resp["result"] = result;
         resp["reason"] = reason;
         std::string body;
         json_util::serialize(resp,body);
         conn->set_status(code);//状态码，200表示ok，400表示bad_request
         conn->set_body(body);
         conn->append_header("Content-Type", "application/json");//这个头千万别忘了加！！！！！！！！！！！！！
         return ;
    }


    void register_handle(wsserver_t:: connection_ptr &conn){
        //2.对正文进行反序列化，获取username和password-------直接从conn中使用get_request_body()获取
        std::string req_body = conn->get_request_body();
        Json::Value log_info;
        bool ret = json_util::unserialize(req_body,log_info);//获取的正文放到log_info中
        if(ret == false)
        {
            DBG_LOG("反序列化失败");
            http_resp(conn,false,websocketpp::http::status_code::bad_request,"请求的正文格式错误");
            return;
        }
        //判断用户名和密码是否完整！！！！！！
        if (log_info["username"].isNull() || log_info["password"].isNull())
        {
            DBG_LOG("用户名/密码不完整");
            http_resp(conn,false,websocketpp::http::status_code::bad_request,"请输入完整的用户名和密码");
            return;
        }

        //3.数据库中添加用户
        ret = _ut.insert(log_info);
        if(ret == false)
        {
            DBG_LOG("向数据库插入数据失败");
            http_resp(conn,false,websocketpp::http::status_code::bad_request,"用户名已经被占用!");
            return ;
        }
        
        http_resp(conn,true,websocketpp::http::status_code::ok,"用户添加成功");
         //成功了status设置为200
         //失败了status设置为400
    }
    //用户注册！！！！


    //从cookie_str中获取ssid---------！！！！！！！！！！
    bool get_cookie_val(const std::string &cookie_str, const std::string &key,  std::string &val){//第一个参数是cookie字符串，第二个参数是key，第三个参数是外部传入的用来存储数据的
        // Cookie: SSID=XXX; path=/; 
        //1. 以 ; 作为间隔，对字符串进行分割，得到各个单个的cookie信息
        std::string sep = "; ";
        std::vector<std::string> cookie_arr;
        string_util::split(cookie_str, sep, cookie_arr);
        for (auto str : cookie_arr) {
            //2. 对单个cookie字符串，以 = 为间隔进行分割，得到key和val
            std::vector<std::string> tmp_arr;
            string_util::split(str, "=", tmp_arr);
            if (tmp_arr.size() != 2) { continue; }
            if (tmp_arr[0] == key) {
                val = tmp_arr[1];
                return true;
            }
        }
        return false;
    }

    void ihfo_handle(wsserver_t:: connection_ptr &conn){
        Json::Value resp;
        //1. 获取请求信息中的Cookie，从Cookie中获取ssid-----在header里面!!!!!!!!!!!!
        std::string cookie_str = conn->get_request_header("Cookie");
        ///*获取http请求头部*/        std::string const & get_request_header(std::string const & key)

        if (cookie_str.empty()) 
        {    //如果没有cookie，返回错误：没有cookie信息，让客户端重新登录
           return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到cookie信息,请重新登录");
        }
         
        // 1.5从cookie中取出ssid
        std::string ssid_str;
        bool ret = get_cookie_val(cookie_str,"SSID",ssid_str);
        if(ret == false )
        {
            //cookie中没有ssid，返回错误：没有ssid信息，让客户端重新登录
            return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到ssid信息，请重新登录");
        }

        // 2. 在session管理中查找对应的会话信息
        
        session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
        if(ssp.get() == nullptr)
        {
            //没有找到session，则认为登录已经过期，需要重新登录
            return http_resp(conn, true, websocketpp::http::status_code::bad_request, "登录过期，请重新登录");
        }

        // 3. 从数据库中取出用户信息，进行序列化发送给客户端
        uint64_t uid = ssp->get_id();
        Json::Value user_info;
        ret = _ut.select_id(user_info,uid);///别忘了直接使用select_id，把所有数据全部放入
        if(ret == false)
        {
            //获取用户信息失败，返回错误：找不到用户信息
            return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到用户信息，请重新登录");
        }

        std::string body;
        json_util::serialize(user_info,body);
        //http报文三件套！！！！！
        conn->set_body(body);
        conn->set_status(websocketpp::http::status_code::ok);
        conn->append_header("Content-Type", "application/json");//设置头为json格式
        // 4. 刷新session的过期时间
        _sm.set_session_expire_time(std::stol(ssid_str),  SESSION_TIMEOUT);
    }

    //获取用户信息-----显示分数，胜局，总局数！！！！


    // HTTP请求回调函数//前几部注册和登录都是http请求，在这里进行！！！！！！！！！！
    void http_callback(websocketpp::connection_hdl hdl)//这个是固定参数！！！！！！！！
    {
      wsserver_t:: connection_ptr conn = _wssrv.get_con_from_hdl(hdl); //获取连接指针
      websocketpp::http::parser::request req = conn->get_request();//使用get_request()获取发送过来的信息的req格式
      std::string method = req.get_method();//post / get
      std::string uri = req.get_uri();//
      //这些报头格式都是设计过的，在前端代码中有对应！！！！！！
      if(method == "POST" && uri == "/reg")
      {
        register_handle(conn);
      }
      else if(method == "POST" && uri == "/login")
      {
        login_handle(conn);
      }
      else if(method == "GET" && uri == "/info")
      {
        ihfo_handle(conn);
      }
      //前三个都是动态响应！！！！！！
      //这个是静态资源请求，
      else
      {
        file_handle(conn);
      }
    }

    //-----------------------------------------------------------------------------------------------------------------------------------------------------------------
    
    //websocket协议的回调函数-------------------------------------------------------------------------------------------------------------
    

    session_ptr get_session_by_cookie(wsserver_t::connection_ptr conn) {
            Json::Value err_resp;
            // 1. 获取请求信息中的Cookie，从Cookie中获取ssid
            std::string cookie_str = conn->get_request_header("Cookie");
            if (cookie_str.empty()) {
                //如果没有cookie，返回错误：没有cookie信息，让客户端重新登录
                err_resp["optype"] = "hall_ready";
                err_resp["reason"] = "没有找到cookie信息，需要重新登录";
                err_resp["result"] = false;
                ws_resp(conn, err_resp);
                return session_ptr();
            }
            // 1.5. 从cookie中取出ssid
            std::string ssid_str;
            bool ret = get_cookie_val(cookie_str, "SSID", ssid_str);
            if (ret == false) {
                //cookie中没有ssid，返回错误：没有ssid信息，让客户端重新登录
                err_resp["optype"] = "hall_ready";
                err_resp["reason"] = "没有找到SSID信息，需要重新登录";
                err_resp["result"] = false;
                ws_resp(conn, err_resp);
                return session_ptr();
            }
            // 2. 在session管理中查找对应的会话信息,痛过ssid
            session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
            if (ssp.get() == nullptr) {
                //没有找到session，则认为登录已经过期，需要重新登录
                err_resp["optype"] = "hall_ready";
                err_resp["reason"] = "没有找到session信息，需要重新登录";
                err_resp["result"] = false;
                ws_resp(conn, err_resp);
                return session_ptr();
            }
            return ssp;
        }

    
    void ws_resp(wsserver_t::connection_ptr conn, Json::Value &resp) 
    {
        std::string body;
        json_util::serialize(resp, body);
        conn->send(body); //websocket相应与http不同，直接调用send（正文即可）
    }


    void wsopen_game_hall(wsserver_t::connection_ptr conn) 
    {
            //游戏大厅长连接建立成功
            Json::Value resp_json;

            //1. 登录验证--判断当前客户端是否已经成功登录
            session_ptr ssp = get_session_by_cookie(conn);
            if (ssp.get() == nullptr) {
                return;
            }
            //2. 判断当前客户端是否是重复登录---------在插入之前先在大厅里和房间里找一下是否已经存在
            if (_om.is_in_hall(ssp->get_id()) || _om.is_in_room(ssp->get_id())) {
                resp_json["optype"] = "hall_ready";
                resp_json["reason"] = "玩家重复登录！";
                resp_json["result"] = false;
                return ws_resp(conn, resp_json);
            }

            //3. 将当前客户端以及连接加入到游戏大厅
            _om.enter_hall(ssp->get_id(), conn);

            //4. 给客户端响应游戏大厅连接建立成功
            resp_json["optype"] = "hall_ready";
            resp_json["result"] = true;
            ws_resp(conn, resp_json);
            
            //5. 记得将session设置为永久存在
            _sm.set_session_expire_time(ssp->ssid(), SESSION_FOREVER);
    }


    void wsopen_game_room(wsserver_t::connection_ptr conn) {
            Json::Value resp_json;
            //1. 获取当前客户端的session
            session_ptr ssp = get_session_by_cookie(conn);
            if (ssp.get() == nullptr) {
                return;
            }
            //2. 当前用户是否已经在在线用户管理的游戏房间或者游戏大厅中---在线用户管理
            if (_om.is_in_hall(ssp->get_id()) || _om.is_in_room(ssp->get_id())) {
                resp_json["optype"] = "room_ready";
                resp_json["reason"] = "玩家重复登录！";
                resp_json["result"] = false;
                return ws_resp(conn, resp_json);
            }
            //3. 判断当前用户是否已经创建好了房间 --- 房间管理
            room_ptr rp = _rm.get_room_by_uid(ssp->get_id());
            if (rp.get() == nullptr) {
                resp_json["optype"] = "room_ready";
                resp_json["reason"] = "没有找到玩家的房间信息";
                resp_json["result"] = false;
                return ws_resp(conn, resp_json);
            }
            //4. 将当前用户添加到在线用户管理的游戏房间中
            _om.enter_room(ssp->get_id(), conn);
            //5. 将session重新设置为永久存在
            _sm.set_session_expire_time(ssp->ssid(), SESSION_FOREVER);
            //6. 回复房间准备完毕
            resp_json["optype"] = "room_ready";
            resp_json["result"] = true;
            resp_json["room_id"] = (Json::UInt64)rp->get_room_id();
            resp_json["uid"] = (Json::UInt64)ssp->get_id();
            resp_json["white_id"] = (Json::UInt64)rp->get_white_id();
            resp_json["black_id"] = (Json::UInt64)rp->get_black_id();
            return ws_resp(conn, resp_json);
        }



    // WebSocket连接打开回调函数
    void wsopen_callback(websocketpp::connection_hdl hdl) 
    {
      //长连接建立之后的回调函数，这里就是指按下匹配按钮后会调用的业务函数！！！！！
      wsserver_t:: connection_ptr conn = _wssrv.get_con_from_hdl(hdl); //获取连接指针
      websocketpp::http::parser::request req = conn->get_request();//使用get_request()获取发送过来的信息的req格式
      std::string uri = req.get_uri();//

      //先从http报文中获取uri，报文后半部分是切换websocket协议的内容，我们不关心

      //WebSocket连接打开-------两种情况------1.hall-游戏大厅打开-----------2.游戏房间打开
       if (uri == "/hall") 
       {
        //建立了游戏大厅的长连接
        return wsopen_game_hall(conn);
       }

       else if (uri == "/room") 
       {
        //建立了游戏房间的长连接
        return wsopen_game_room(conn);
       }

       //这两种情况对应不同的处理！！！！！
    } 


    void wsclose_game_hall(wsserver_t::connection_ptr conn) {
            //游戏大厅长连接断开的处理
            //1. 登录验证--判断当前客户端是否已经成功登录
            session_ptr ssp = get_session_by_cookie(conn);
            if (ssp.get() == nullptr) {
                return;
            }
            //1. 将玩家从游戏大厅中移除
            _om.exit_hall(ssp->get_id());
            //2. 将session恢复生命周期的管理，设置定时销毁
            _sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);
        }

        void wsclose_game_room(wsserver_t::connection_ptr conn) {
            //获取会话信息，识别客户端
            session_ptr ssp = get_session_by_cookie(conn);
            if (ssp.get() == nullptr) {
                return;
            }

            DBG_LOG("有用户关闭了room长连接");
            //1. 将玩家从在线用户管理中移除
            _om.exit_room(ssp->get_id());
            //2. 将session回复生命周期的管理，设置定时销毁
            _sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);
            //3. 将玩家从游戏房间中移除，房间中所有用户退出了就会销毁房间
            _rm.remove_room_user(ssp->get_id());
        }


    // WebSocket连接关闭回调函数-----------------------------------当我们关闭页面或者切换为之前的login页面时需要把websocket长连接关闭，并且使用户退出房间/大厅
    void wsclose_callback(websocketpp::connection_hdl hdl)
    {
       //长连接建立之后的回调函数，这里就是指按下匹配按钮后会调用的业务函数！！！！！
      wsserver_t:: connection_ptr conn = _wssrv.get_con_from_hdl(hdl); //获取连接指针
      websocketpp::http::parser::request req = conn->get_request();//使用get_request()获取发送过来的信息的req格式
      std::string uri = req.get_uri();//

      //先从http报文中获取uri，报文后半部分是切换websocket协议的内容，我们不关心

      //WebSocket连接打开-------两种情况------1.hall-游戏大厅打开-----------2.游戏房间打开
       if (uri == "/hall") 
       {
        //关闭了游戏大厅的长连接
        return wsclose_game_hall(conn);
       }

       else if (uri == "/room") 
       {
        //关闭了游戏房间的长连接
        return wsclose_game_room(conn);
       }

       //这两种情况对应不同的处理！！！！！
    }

    void wsmsg_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) {
            Json::Value resp_json;
            std::string resp_body;
            //1. 身份验证，当前客户端到底是哪个玩家
            session_ptr ssp = get_session_by_cookie(conn);
            if (ssp.get() == nullptr) {
                return;
            }
            //2. 获取请求信息
            std::string req_body = msg->get_payload();//从wsserver_t::message_ptr 中获取正文要使用get_payload()
            Json::Value req_json;
            bool ret = json_util::unserialize(req_body, req_json);//反序列化
            if (ret == false) {
                resp_json["result"] = false;
                resp_json["reason"] = "请求信息解析失败";
                return ws_resp(conn, resp_json);
            }
            //3. 对于请求进行处理：
            if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_start"){
                //  开始对战匹配：通过匹配模块，将用户添加到匹配队列中
                _mm.add(ssp->get_id());
                resp_json["optype"] = "match_start";
                resp_json["result"] = true;
                return ws_resp(conn, resp_json);
            }else if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_stop") {
                //  停止对战匹配：通过匹配模块，将用户从匹配队列中移除
                _mm.del(ssp->get_id());
                resp_json["optype"] = "match_stop";
                resp_json["result"] = true;
                return ws_resp(conn, resp_json);
            }
            resp_json["optype"] = "unknow";
            resp_json["reason"] = "请求类型未知";
            resp_json["result"] = false;
            return ws_resp(conn, resp_json);
        }


        void wsmsg_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) {
            Json::Value resp_json;
            //1. 获取客户端session，识别客户端身份
            session_ptr ssp = get_session_by_cookie(conn);
            if (ssp.get() == nullptr) {
                DBG_LOG("房间-没有找到会话信息");
                return;
            }
            //2. 获取客户端房间信息
            room_ptr rp = _rm.get_room_by_uid(ssp->get_id());
            if (rp.get() == nullptr) {
                resp_json["optype"] = "unknow";
                resp_json["reason"] = "没有找到玩家的房间信息";
                resp_json["result"] = false;
                DBG_LOG("房间-没有找到玩家房间信息");
                return ws_resp(conn, resp_json);
            }
            //3. 对消息进行反序列化
            Json::Value req_json;
            std::string req_body = msg->get_payload();
            bool ret = json_util::unserialize(req_body, req_json);
            if (ret == false) {
                resp_json["optype"] = "unknow";
                resp_json["reason"] = "请求解析失败";
                resp_json["result"] = false;
                DBG_LOG("房间-反序列化请求失败");
                return ws_resp(conn, resp_json);
            }
           // DBG_LOG("房间：收到房间请求，开始处理....");
            //4. 通过房间模块进行消息请求的处理
            return rp->handle_request(req_json);
        }
    

    // WebSocket消息接收回调函数
    void wsmessage_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg)
    {
        //websocket长连接通信处理
            wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);
            websocketpp::http::parser::request req = conn->get_request();
            std::string uri = req.get_uri();
            if (uri == "/hall") {
                //建立了游戏大厅的长连接
                return wsmsg_game_hall(conn, msg);
            }else if (uri == "/room") {
                //建立了游戏房间的长连接
                return wsmsg_game_room(conn, msg);
            }
    }

public:
    gobang_server(const std::string &host,
              const std::string &username,
              const std::string &password,
              const std::string &dbname,
              uint16_t port = 3306,
              const std::string &wwwroot = WWWROOT)
    :_ut(host, username, password, dbname, port)
    ,_web_root(wwwroot)
    ,_om()
    ,_rm(&_ut,&_om)
    ,_mm(&_rm, &_ut, &_om)
    ,_sm(&_wssrv)
    {
    // 2.设置日志级别：none表示不打印任何日志
    _wssrv.set_access_channels(websocketpp::log::alevel::none);
    // 3.初始化ASIO网络库
    _wssrv.init_asio();
    _wssrv.set_reuse_addr(true);
    // 4.注册HTTP请求处理回调函数
    _wssrv.set_http_handler(std::bind(&gobang_server::http_callback,this,std::placeholders::_1));
    // 4.注册WebSocket连接打开回调函数
    _wssrv.set_open_handler(std::bind(&gobang_server::wsopen_callback,this,std::placeholders::_1));
    // 4.注册WebSocket连接关闭回调函数
    _wssrv.set_close_handler(std::bind(&gobang_server::wsclose_callback,this,std::placeholders::_1));
    // 4.注册WebSocket消息接收回调函数
    _wssrv.set_message_handler(std::bind(&gobang_server::wsmessage_callback,this,std::placeholders::_1,std::placeholders::_2));
    }

    void start(int port)
    {
    // 5.监听8081端口
    _wssrv.listen(8081);
    // 6.开始接受TCP连接
    _wssrv.start_accept();
    // 7.运行服务器（进入事件循环）
    _wssrv.run();
    }
};



#endif 